Add an option `event_headers_style` to PostAgent

It is defaulted to `capitalized`, normalizing emitted headers by
capitalizing the names so downstream agents don't need to bother with
the letter cases.

Previously, the style of header names in an emitted event would vary
depending on the server and the backend HTTP library. Most notably, the
`net_http` backend downcases header names whereas other backends do not.

For backward compatibility, preexisting PostAgents are automatically
configured to have `event_headers_style` set to `raw` via migration.

Akinori MUSHA 9 years ago
parent
commit
b095317e4c

+ 41 - 1
app/models/agents/post_agent.rb

@@ -25,6 +25,13 @@ module Agents
25 25
 
26 26
       If `emit_events` is set to `true`, the server response will be emitted as an Event and can be fed to a WebsiteAgent for parsing (using its `data_from_event` and `type` options). No data processing
27 27
       will be attempted by this Agent, so the Event's "body" value will always be raw text.
28
+      The Event will also have a "headers" hash and a "status" integer value.
29
+      Set `event_headers_style` to one of the following values to normalize the keys of "headers" for downstream agents' convenience:
30
+
31
+        * `capitalized` (default) - Header names are capitalized; e.g. "Content-Type"
32
+        * `downcased` - Header names are downcased; e.g. "content-type"
33
+        * `snakecased` - Header names are snakecased; e.g. "content_type"
34
+        * `raw` - Backward compatibility option to leave them unmodified from what the underlying HTTP library returns.
28 35
 
29 36
       Other Options:
30 37
 
@@ -92,6 +99,12 @@ module Agents
92 99
         errors.add(:base, "if provided, emit_events must be true or false")
93 100
       end
94 101
 
102
+      begin
103
+        normalize_response_headers({})
104
+      rescue ArgumentError => e
105
+        errors.add(:base, e.message)
106
+      end
107
+
95 108
       unless %w[post get put delete patch].include?(method)
96 109
         errors.add(:base, "method must be 'post', 'get', 'put', 'delete', or 'patch'")
97 110
       end
@@ -124,6 +137,29 @@ module Agents
124 137
 
125 138
     private
126 139
 
140
+    def normalize_response_headers(headers)
141
+      case interpolated['event_headers_style']
142
+      when nil, '', 'capitalized'
143
+        normalize = ->name {
144
+          name.gsub(/(?:\A|(?<=-))([[:alpha:]])|([[:alpha:]]+)/) {
145
+            $1 ? $1.upcase : $2.downcase
146
+          }
147
+        }
148
+      when 'downcased'
149
+        normalize = :downcase.to_proc
150
+      when 'snakecased', nil
151
+        normalize = ->name { name.tr('A-Z-', 'a-z_') }
152
+      when 'raw'
153
+        normalize = ->name { name }  # :itself.to_proc in Ruby >= 2.2
154
+      else
155
+        raise ArgumentError, "if provided, event_headers_style must be 'capitalized', 'downcased', 'snakecased' or 'raw'"
156
+      end
157
+
158
+      headers.each_with_object({}) { |(key, value), hash|
159
+        hash[normalize[key]] = value
160
+      }
161
+    end
162
+
127 163
     def handle(data, payload = {})
128 164
       url = interpolated(payload)[:post_url]
129 165
       headers = headers()
@@ -156,7 +192,11 @@ module Agents
156 192
       }
157 193
 
158 194
       if boolify(interpolated['emit_events'])
159
-        create_event payload: { body: response.body, headers: response.headers, status: response.status }
195
+        create_event payload: {
196
+          body: response.body,
197
+          headers: normalize_response_headers(response.headers),
198
+          status: response.status
199
+        }
160 200
       end
161 201
     end
162 202
   end

+ 11 - 0
db/migrate/20160405072512_post_agent_set_event_header_style.rb

@@ -0,0 +1,11 @@
1
+class PostAgentSetEventHeaderStyle < ActiveRecord::Migration
2
+  def up
3
+    Agent.of_type("Agents::PostAgent").each do |post_agent|
4
+      if post_agent.send(:boolify, post_agent.options['emit_events']) &&
5
+         !post_agent.options.key?('event_headers_style')
6
+        post_agent.options['event_headers_style'] = 'raw'
7
+        post_agent.save!
8
+      end
9
+    end
10
+  end
11
+end

+ 20 - 2
spec/models/agents/post_agent_spec.rb

@@ -52,7 +52,7 @@ describe Agents::PostAgent do
52 52
           raise "unexpected Content-Type: #{content_type}"
53 53
         end
54 54
       end
55
-      { status: 200, body: "<html>a webpage!</html>", headers: { 'Content-Type' => 'text/html' } }
55
+      { status: 200, body: "<html>a webpage!</html>", headers: { 'Content-type' => 'text/html' } }
56 56
     }
57 57
   end
58 58
 
@@ -226,10 +226,28 @@ describe Agents::PostAgent do
226 226
           expect(@checker.events.last.payload['body']).to eq '<html>a webpage!</html>'
227 227
         end
228 228
 
229
-        it "emits the response headers" do
229
+        it "emits the response headers capitalized by default" do
230 230
           @checker.check
231 231
           expect(@checker.events.last.payload['headers']).to eq({ 'Content-Type' => 'text/html' })
232 232
         end
233
+
234
+        it "emits the response headers capitalized" do
235
+          @checker.options['event_headers_style'] = 'capitalized'
236
+          @checker.check
237
+          expect(@checker.events.last.payload['headers']).to eq({ 'Content-Type' => 'text/html' })
238
+        end
239
+
240
+        it "emits the response headers downcased" do
241
+          @checker.options['event_headers_style'] = 'downcased'
242
+          @checker.check
243
+          expect(@checker.events.last.payload['headers']).to eq({ 'content-type' => 'text/html' })
244
+        end
245
+
246
+        it "emits the response headers snakecased" do
247
+          @checker.options['event_headers_style'] = 'snakecased'
248
+          @checker.check
249
+          expect(@checker.events.last.payload['headers']).to eq({ 'content_type' => 'text/html' })
250
+        end
233 251
       end
234 252
     end
235 253
   end